Requirements

# load required libraries

library(quanteda)
library(readtext)
#library(corpus)
#library(tidyverse)
#library(stringr)
#library(tidytext)
#library(harrypotter)
#library(dplyr)
library(quanteda.sentiment)
library(vader)
#library(caret)
#library(reshape2)


#require(quanteda)
#require(quanteda.corpora)
#require(quanteda.sentiment)

1. Step: Load Corpus Data & Sentiment Lexicons

# ===== DATASETS =====
# load BASELINE datasets
reviews <- readRDS(file="datasets/baseline/books_config_base.rds")
twitter <- readRDS(file="datasets/baseline/twitter_config_base.rds")
parlvote <- readRDS(file="datasets/baseline/parl_config_base.rds")
amazon <- readRDS(file="datasets/baseline/ama_config_base.rds")
finance <- readRDS(file="datasets/baseline/fin_config_base.rds")
parlvote_corr <- readRDS(file="datasets/corr_part.rds") 

# load preprocessed datasets
reviews_config2 <- readRDS(file="datasets/config_2/book_config_2.rds")
twitter_config2 <- readRDS(file="datasets/config_2/twitter_config_2.rds")
parlvote_config2 <- readRDS(file="datasets/config_2/parl_config_2.rds")
amazon_config2 <- readRDS(file="datasets/config_2/ama_config_2.rds")
finance_config2 <- readRDS(file="datasets/config_2/fin_config_2.rds")

# ===== SENTIMENT LEXICONS ======
# load lexicons
afinn <- data_dictionary_AFINN
lsd <- data_dictionary_LSD2015
Look at Example Corpus: ParlVote

Each corpus data frame consists of:
- ID: original document id
- Text: will be the input to the sentiment analysis
- Rating: will be the gold standard to evaluate lexicon performance

parlvote

2. Step: Sentiment Analysis

2.1 Normalize Scores

We use a customized min/max normalization method here, to make sure all values of the Afinn lexicon are scaled with respect to Afinn’s minimum label (-5) and maximum label (+5). The computed sentiment scores will be normalized between -1 and +1.

# ===== DATA NORMALIZATION =====

# Normalize data via minimun/maximum normalization, either by scaling values from 0 to 1 or -1 to 1
# 
# Arg:
#   x: input values (e.g. column of data frame)
#   
# Returns: 
#   normalized data

# min/max normalization from -1 to 1, relative to data frame results
normalize <- function(x, na.rm = TRUE){
  return(2* ((x - min(x)) / (max(x)-min(x)))-1)}

# min/max normalization for afinn data, wrt to afinn scoring (-5, +5) 
normalize_afinn <- function(x, na.rm = TRUE){
  return(2* ((x - (-5)) / (5-(-5)))-1)}

2.2 Compute Sentiment Scores

In this step sentiment scores for the different lexicons (Afinn, LSD and Vader) are calculated by using the texstat_valence (Afinn) and textstat_polarity (LSD) functions of the quanteda.sentiment library and the vader_compound score of the vader library. Originally two versions of normalization were tested. For the final sentiment scoring we decided on the normalize_afinn function.

# ===== SENTIMENT SCORES =====

# Calculate sentiment scores for different lexicons and input data frames
# 
# Arg:
#  data: input data frame
#  lexicons: names of lexicons that should be used for sentiment scoring
#  normalize: "relative" if normalization is handled relative to output, i.e. output column of afinn is being scaled via min/max normalization
#  normalize: "afinn" if normalization is handled by taking -5 as new minimum and +5 as new maximum and everything else is scaled between
#  get_tokens: if TRUE final data frame consists of single tokens with an associated sentiment score
#              else final data frame consists of an associated sentiment score per input text instead (only used to calculate TOP-N words)
#   
# Returns: 
#  data frame with (normalized) sentiment sores for chosen lexicons

get_sentiment <- function(df, lexicons, normalize, get_tokens){
   
   # if we want data frame with single tokens 
   if(get_tokens==TRUE){
      df <- df %>%
         # get list of tokens as new col in data frame
         unnest_tokens(token,text)
      
      # assign the new col as input for sentiment lexicons
      tok = df$token
     
      # for each lexicon, get sentiment scores and save scores in new column of data frame
      for(lex in lexicons){
         
         if(lex == "afinn"){
            df$afinn <- round(textstat_valence(tok, afinn, normalize="dictionary")$sentiment,3)}
         if(lex == "lsd"){
            df$lsd<- round(textstat_polarity(tok, lsd, fun=sent_relpropdiff)$sentiment,3)}
     
         if(lex == "vader"){
            df$vader <- round(vader_df(tok)$compound,3)}
   }
    
   # if we don't want to analyze single tokens but input text as "whole"
   }else{
      
      tok = tokens(df$text)
      
      for(lex in lexicons){
         if(lex == "afinn"){
            df$afinn <- round(textstat_valence(tok, afinn, normalize="dictionary")$sentiment,3)}
         
         if(lex == "lsd"){
            df$lsd<- round(textstat_polarity(tok, lsd, fun=sent_relpropdiff)$sentiment,3)}
         
         if(lex == "vader"){
            df$vader <- round(vader_df(df$text)$compound,3)}
     }
   }  
   
   # normalize sentiment scores if TRUE, VADER and LSD scores are already normalized within functions above

   if(normalize=="afinn"){
      df$afinn <- round(normalize_afinn(df$afinn), 3)
   }else{
      df$afinn <- round(normalize(df$afinn), 3)}
   
   # set sentiment scores to 0 if NA
   df[is.na(df)] <- 0
   
   return(df)
}

# apply get_sentiment function to corpus data 
### BASELINE  
reviews_sentiment_norm1 <- get_sentiment(reviews, c("afinn","lsd", "vader"), normalize="afinn", get_tokens=FALSE)
twitter_sentiment_norm1 <- get_sentiment(twitter, c("afinn","lsd", "vader"), normalize="afinn", get_tokens=FALSE)
parlvote_sentiment_norm1 <- get_sentiment(parlvote, c("afinn","lsd", "vader"), normalize="afinn", get_tokens=FALSE)
amazon_sentiment_norm1 <- get_sentiment(amazon, c("afinn", "lsd", "vader"), normalize="afinn", get_tokens=FALSE)
finance_sentiment_norm1 <- get_sentiment(finance, c("afinn", "lsd", "vader"), normalize="afinn", get_tokens=FALSE)
parlvote_corr_norm1 <- get_sentiment(parlvote_corr, c("afinn", "lsd", "vader"), normalize="afinn", get_tokens=FALSE)

### CONFIG2
reviews_config2_sentiment_norm1 <- get_sentiment(reviews_config2, c("afinn","lsd", "vader"), normalize="afinn", get_tokens=FALSE)
twitter_config2_sentiment_norm1 <- get_sentiment(twitter_config2, c("afinn","lsd", "vader"), normalize="afinn", get_tokens=FALSE)
parlvote_config2_sentiment_norm1 <- get_sentiment(parlvote_config2, c("afinn","lsd", "vader"), normalize="afinn", get_tokens=FALSE)
amazon_config2_sentiment_norm1 <- get_sentiment(amazon_config2, c("afinn", "lsd", "vader"), normalize="afinn", get_tokens=FALSE)
finance_config2_sentiment_norm1 <- get_sentiment(finance_config2, c("afinn", "lsd", "vader"), normalize="afinn", get_tokens=FALSE)

3. Step (Optional): Convert Data into discrete format

# Convert final values into ternary (1 = positive, 0 = neutral, -1 = negative) format for evaluation and comparison
# 
# Arg:
#   df: input data frame that contains the columns to be converted into ternary format
#   to_change: column names that should be converted into ternary format
#   
# Returns: 
#   data frame with converted values

get_discrete <- function(df, to_change){
  df %>% 
    mutate_at(to_change, function(x){
      # mutate values greater than 0 to 1 (positive), equal to 0 to 0 (neutral) and smaller than 0 to -1 (negative)
      case_when(x > 0 ~ 1, x < 0 ~ -1, x == 0 ~ 0)})# %>% 
}

reviews_discrete <- get_discrete(reviews_sentiment_norm1, c("afinn","vader","lsd"))
twitter_discrete <- get_discrete(twitter_sentiment_norm1, c("afinn","vader","lsd"))
parlvote_discrete <- get_discrete(parlvote_sentiment_norm1, c("afinn","vader","lsd"))
amazon_discrete <- get_discrete(amazon_sentiment_norm1, c("afinn","vader","lsd"))
finance_discrete <- get_discrete(finance_sentiment_norm1, c("afinn","vader","lsd"))

4. Step: Compute Coverage

To compute the coverage of each sentiment lexicon and for each corpus, we consider all texts or tokens receiving a score of 0 to be not-recognized by the according lexicon, i.e. the word does not exist in the lexicon or the whole text did not contain a word which is contained in the lexicon. We compute the coverage per token and the coverage per text. In case of the coverage per token, we consider 2 configurations: 1) a baseline configuration with a simple token-count to get the coverage score, 2) a configuration of the tokens with a prior stopword removal.

# get sentiment for each token in corpus
reviews_tok_sent <- get_sentiment(reviews, c("afinn","lsd", "vader"), normalize="afinn", get_tokens=TRUE)
twitter_tok_sent <- get_sentiment(twitter, c("afinn","lsd", "vader"), normalize="afinn", get_tokens=TRUE)
parlvote_tok_sent <- get_sentiment(parlvote, c("afinn","lsd", "vader"), normalize="afinn", get_tokens=TRUE)
amazon_tok_sent <- get_sentiment(amazon, c("afinn","lsd", "vader"), normalize="afinn", get_tokens=TRUE)
finance_tok_sent <- get_sentiment(finance, c("afinn", "lsd", "vader"), normalize="afinn", get_tokens=TRUE)


# convert values to discrete format 
reviews_tok_discrete <- get_discrete(reviews_tok_sent, c("afinn","vader","lsd"))
twitter_tok_discrete <- get_discrete(twitter_tok_sent, c("afinn","vader","lsd"))
parlvote_tok_discrete <- get_discrete(parlvote_tok_sent, c("afinn","vader","lsd"))
amazon_tok_discrete <- get_discrete(amazon_tok_sent, c("afinn","vader","lsd"))
finance_tok_discrete <- get_discrete(finance_tok_sent, c("afinn","vader","lsd"))
# ===== COVERAGE PER TOKEN =====

# Coverage per token: if sentiment score is not zero, count as "covered", get percentage of covered tokens
reviews_tok_coverage <- `rownames<-`(data.frame(t(round(colSums(reviews_tok_sent[c("afinn","lsd","vader")] != 0)/colSums(reviews_tok_sent != 0)[[1]]*100,2))), "reviews tokens coverage (%)")
twitter_tok_coverage <- `rownames<-`(data.frame(t(round(colSums(twitter_tok_sent[c("afinn","lsd","vader")] != 0)/colSums(twitter_tok_sent != 0)[[1]]*100,2))), "twitter tokens coverage (%)")
parlvote_tok_coverage <- `rownames<-`(data.frame(t(round(colSums(parlvote_tok_sent[c("afinn","lsd","vader")] != 0)/colSums(parlvote_tok_sent != 0)[[1]]*100,2))), "parlvote tokens coverage (%)")
amazon_tok_coverage <- `rownames<-`(data.frame(t(round(colSums(amazon_tok_sent[c("afinn","lsd","vader")] != 0)/colSums(amazon_tok_sent != 0)[[1]]*100,2))), "amazon tokens coverage (%)")
finance_tok_coverage <- `rownames<-`(data.frame(t(round(colSums(finance_tok_sent[c("afinn","lsd","vader")] != 0)/colSums(finance_tok_sent != 0)[[1]]*100,2))), "finance tokens coverage (%)")

# Coverage per token: with stopword removal 

# remove stopwords from each data frame 
reviews_tok.stopwords <- reviews_tok_sent %>%
   anti_join(stop_words, by= c("token" = "word") ) %>%
   ungroup()

twitter_tok.stopwords <- twitter_tok_sent %>%
   anti_join(stop_words, by= c("token" = "word") ) %>%
   ungroup()

parlvote_tok.stopwords <- parlvote_tok_sent %>%
   anti_join(stop_words, by= c("token" = "word") ) %>%
   ungroup()

amazon_tok.stopwords <- amazon_tok_sent %>%
   anti_join(stop_words, by= c("token" = "word") ) %>%
   ungroup()

finance_tok.stopwords <- finance_tok_sent %>%
   anti_join(stop_words, by= c("token" = "word") ) %>%
   ungroup()

# same as above: if sentiment score is not zero, count as "covered", get percentage of covered tokens
reviews_tok_coverage.stopwords <- `rownames<-`(data.frame(t(round(colSums(reviews_tok.stopwords[c("afinn","lsd","vader")] != 0)/colSums(reviews_tok.stopwords != 0)[[1]]*100,2))), "reviews tokens coverage (%) - stopwords")
twitter_tok_coverage.stopwords <- `rownames<-`(data.frame(t(round(colSums(twitter_tok.stopwords[c("afinn","lsd","vader")] != 0)/colSums(twitter_tok.stopwords != 0)[[1]]*100,2))), "twitter tokens coverage (%) - stopwords")
parlvote_tok_coverage.stopwords <- `rownames<-`(data.frame(t(round(colSums(parlvote_tok.stopwords[c("afinn","lsd","vader")] != 0)/colSums(parlvote_tok.stopwords != 0)[[1]]*100,2))), "parlvote tokens coverage (%) - stopwords")
amazon_tok_coverage.stopwords <- `rownames<-`(data.frame(t(round(colSums(amazon_tok.stopwords[c("afinn","lsd","vader")] != 0)/colSums(amazon_tok.stopwords != 0)[[1]]*100,2))), "amazon tokens coverage (%) - stopwords")
finance_tok_coverage.stopwords <- `rownames<-`(data.frame(t(round(colSums(finance_tok.stopwords[c("afinn","lsd","vader")] != 0)/colSums(finance_tok.stopwords != 0)[[1]]*100,2))), "finance tokens coverage (%) - stopwords")

# ===== COVERAGE PER TEXT =====

# Coverage per text: if sentiment score is not zero, count as "covered", get percentage of covered text instances
reviews_coverage <- `rownames<-`(data.frame(t(colSums(reviews_sentiment_norm1[c("afinn","lsd","vader")] != 0)/10)), "reviews text coverage (%)")
twitter_coverage <- `rownames<-`(data.frame(t(colSums(twitter_sentiment_norm1[c("afinn","lsd","vader")] != 0)/10)), "twitter text coverage (%)")
parlvote_coverage <- `rownames<-`(data.frame(t(colSums(parlvote_sentiment_norm1[c("afinn","lsd","vader")] != 0)/10)), "parlvote text coverage (%)")
amazon_coverage <- `rownames<-`(data.frame(t(colSums(amazon_sentiment_norm1[c("afinn","lsd","vader")] != 0)/10)), "amazon text coverage (%)")
finance_coverage <- `rownames<-`(data.frame(t(colSums(finance_sentiment_norm1[c("afinn","lsd","vader")] != 0)/10)), "finance text coverage (%)")

# save data to data frame 
coverage <- rbind(reviews_tok_coverage,reviews_tok_coverage.stopwords,reviews_coverage, twitter_tok_coverage,twitter_tok_coverage.stopwords,parlvote_tok_coverage,twitter_coverage,parlvote_tok_coverage.stopwords, parlvote_coverage, amazon_tok_coverage,amazon_tok_coverage.stopwords, amazon_coverage, finance_tok_coverage,finance_tok_coverage.stopwords, finance_coverage)

Display Coverage Results

5. Step: Plot Data

5.1 Plot Sentiment Scores

# ===== PLOT SENTIMENT SCORES =====

# create data frame with sentiment scores as variable of first 100 instances of corpus
reviews_df <- melt(head(reviews_sentiment_norm1,100)[,c('id','afinn','lsd','vader')],id.vars = 1)
twitter_df <- melt(head(twitter_sentiment_norm1,100)[,c('id','afinn','lsd','vader')],id.vars = 1)
parlvote_df <- melt(head(parlvote_sentiment_norm1,100)[,c('id','afinn','lsd','vader')],id.vars = 1)
amazon_df <- melt(head(amazon_sentiment_norm1,100)[,c('id','afinn','lsd','vader')],id.vars = 1)
finance_df <- melt(head(finance_sentiment_norm1,100)[,c('id','afinn','lsd','vader')],id.vars = 1)

# create plots for each corpus
reviews_plot <- ggplot(reviews_df,aes(x = id,y = value)) + 
                geom_bar(aes(fill = variable),stat = "identity",position = "dodge") +
                facet_wrap(~ variable, ncol = 1, scales="free_y")+
                xlab("text id")+ ylab("sentiment score")+
                ggtitle("Reviews Sentiment")

twitter_plot <- ggplot(twitter_df,aes(x = id,y = value)) + 
                geom_bar(aes(fill = variable),stat = "identity",position = "dodge") +
                xlab("text id")+ ylab("sentiment score")+
                ggtitle("Twitter Sentiment")

parlvote_plot <- ggplot(parlvote_df,aes(x = id,y = value)) + 
                 geom_bar(aes(fill = variable),stat = "identity",position = "dodge")+
                 #facet_wrap(~ variable, ncol = 1, scales="free_y")+
                 xlab("text id")+ ylab("sentiment score")+
                 ggtitle("ParlVote Sentiment")

amazon_plot <- ggplot(amazon_df,aes(x = id,y = value)) + 
               geom_bar(aes(fill = variable),stat = "identity",position = "dodge")+
               #facet_wrap(~ variable, ncol = 1, scales="free_y")+
               xlab("text id")+ ylab("sentiment score")+
               ggtitle("Amazon Sentiment")

finance_plot <- ggplot(finance_df,aes(x = id,y = value)) + 
                geom_bar(aes(fill = variable),stat = "identity",position = "dodge")+
                #facet_wrap(~ variable, ncol = 1, scales="free_y")+
                xlab("text id")+ ylab("sentiment score")+
                ggtitle("Finance Sentiment")

reviews_plot.line <- ggplot(reviews_df,aes(x = id, y = value, group=variable)) +
                     geom_line(aes(colour=variable), size=0.4)+ 
                     ylim(-1,1) +
                     ggtitle("Reviews Sentiment Scores")

# show plots
reviews_plot

reviews_plot.line

twitter_plot

parlvote_plot

amazon_plot

finance_plot 

5.2 Plot Important Words

To plot important words per sentiment we use the sentiment scores for each token (that we calculated for the coverage part) and convert continuous scores to a discrete format.

# load stopwords 
data(stop_words)

In the next step, word counts for each discrete (sentiment) variable (“positive”, “negative”, “neutral”) are retrieved for each lexicon.

# get word counts per discrete variable ("positive", "negative", "neutral", i.e. 1,-1,0)
reviews_afinn.word_counts <- reviews_tok_discrete %>%
   count(token, afinn, sort = TRUE) %>%
   anti_join(stop_words, by= c("token" = "word") ) %>%
   ungroup()

reviews_lsd.word_counts <- reviews_tok_discrete %>%
   count(token, lsd, sort = TRUE) %>%
   anti_join(stop_words, by= c("token" = "word") ) %>%
   ungroup()

reviews_vader.word_counts <- reviews_tok_discrete %>%
   count(token, vader, sort = TRUE) %>%
   anti_join(stop_words, by= c("token" = "word") ) %>%
   ungroup()

Now we can plot the top 20 words for each sentiment.

# plot top n words for each sentiment and for each lexicon, n = 20
topn_reviews_afinn.plot <- reviews_afinn.word_counts %>%
         group_by(afinn) %>%
         slice(1:20) %>%
         ggplot(aes(reorder(token, n), n, fill = afinn)) +
           geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
           facet_wrap(~afinn, scales = "free_y") +
           labs(y = "contribution to sentiment", x = NULL) +
           coord_flip()+
           ggtitle("Reviews: Top 20 Words per Sentiment (Afinn)")
 
topn_reviews_lsd.plot <- reviews_lsd.word_counts %>%
         group_by(lsd) %>%
         slice(1:20) %>%
         ggplot(aes(reorder(token, n), n, fill = lsd)) +
           geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
           facet_wrap(~lsd, scales = "free_y") +
           labs(y = "contribution to sentiment", x = NULL) +
           coord_flip()+
           ggtitle("Reviews: Top 20 Words per Sentiment (LSD)")
 
topn_reviews_vader.plot <- reviews_vader.word_counts %>%
         group_by(vader) %>%
         slice(1:20) %>%
         ggplot(aes(reorder(token, n), n, fill = vader)) +
           geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
           facet_wrap(~vader, scales = "free_y") +
           labs(y = "contribution to sentiment", x = NULL) +
           coord_flip()+
           ggtitle("Reviews: Top 20 Words per Sentiment (VADER)")

topn_reviews_afinn.plot

topn_reviews_lsd.plot

topn_reviews_vader.plot

LS0tCnRpdGxlOiAiU2VudGltZW50IFRvb2xzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIyMgUmVxdWlyZW1lbnRzCmBgYHtyfQojIGxvYWQgcmVxdWlyZWQgbGlicmFyaWVzCgpsaWJyYXJ5KHF1YW50ZWRhKQpsaWJyYXJ5KHJlYWR0ZXh0KQojbGlicmFyeShjb3JwdXMpCiNsaWJyYXJ5KHRpZHl2ZXJzZSkKI2xpYnJhcnkoc3RyaW5ncikKI2xpYnJhcnkodGlkeXRleHQpCiNsaWJyYXJ5KGhhcnJ5cG90dGVyKQojbGlicmFyeShkcGx5cikKbGlicmFyeShxdWFudGVkYS5zZW50aW1lbnQpCmxpYnJhcnkodmFkZXIpCiNsaWJyYXJ5KGNhcmV0KQojbGlicmFyeShyZXNoYXBlMikKCgojcmVxdWlyZShxdWFudGVkYSkKI3JlcXVpcmUocXVhbnRlZGEuY29ycG9yYSkKI3JlcXVpcmUocXVhbnRlZGEuc2VudGltZW50KQpgYGAKCiMjIyAxLiBTdGVwOiBMb2FkIENvcnB1cyBEYXRhICYgU2VudGltZW50IExleGljb25zCmBgYHtyfQojID09PT09IERBVEFTRVRTID09PT09CiMgbG9hZCBCQVNFTElORSBkYXRhc2V0cwpyZXZpZXdzIDwtIHJlYWRSRFMoZmlsZT0iZGF0YXNldHMvYmFzZWxpbmUvYm9va3NfY29uZmlnX2Jhc2UucmRzIikKdHdpdHRlciA8LSByZWFkUkRTKGZpbGU9ImRhdGFzZXRzL2Jhc2VsaW5lL3R3aXR0ZXJfY29uZmlnX2Jhc2UucmRzIikKcGFybHZvdGUgPC0gcmVhZFJEUyhmaWxlPSJkYXRhc2V0cy9iYXNlbGluZS9wYXJsX2NvbmZpZ19iYXNlLnJkcyIpCmFtYXpvbiA8LSByZWFkUkRTKGZpbGU9ImRhdGFzZXRzL2Jhc2VsaW5lL2FtYV9jb25maWdfYmFzZS5yZHMiKQpmaW5hbmNlIDwtIHJlYWRSRFMoZmlsZT0iZGF0YXNldHMvYmFzZWxpbmUvZmluX2NvbmZpZ19iYXNlLnJkcyIpCnBhcmx2b3RlX2NvcnIgPC0gcmVhZFJEUyhmaWxlPSJkYXRhc2V0cy9jb3JyX3BhcnQucmRzIikgCgojIGxvYWQgcHJlcHJvY2Vzc2VkIGRhdGFzZXRzCnJldmlld3NfY29uZmlnMiA8LSByZWFkUkRTKGZpbGU9ImRhdGFzZXRzL2NvbmZpZ18yL2Jvb2tfY29uZmlnXzIucmRzIikKdHdpdHRlcl9jb25maWcyIDwtIHJlYWRSRFMoZmlsZT0iZGF0YXNldHMvY29uZmlnXzIvdHdpdHRlcl9jb25maWdfMi5yZHMiKQpwYXJsdm90ZV9jb25maWcyIDwtIHJlYWRSRFMoZmlsZT0iZGF0YXNldHMvY29uZmlnXzIvcGFybF9jb25maWdfMi5yZHMiKQphbWF6b25fY29uZmlnMiA8LSByZWFkUkRTKGZpbGU9ImRhdGFzZXRzL2NvbmZpZ18yL2FtYV9jb25maWdfMi5yZHMiKQpmaW5hbmNlX2NvbmZpZzIgPC0gcmVhZFJEUyhmaWxlPSJkYXRhc2V0cy9jb25maWdfMi9maW5fY29uZmlnXzIucmRzIikKCiMgPT09PT0gU0VOVElNRU5UIExFWElDT05TID09PT09PQojIGxvYWQgbGV4aWNvbnMKYWZpbm4gPC0gZGF0YV9kaWN0aW9uYXJ5X0FGSU5OCmxzZCA8LSBkYXRhX2RpY3Rpb25hcnlfTFNEMjAxNQpgYGAKCiMjIyMjIExvb2sgYXQgRXhhbXBsZSBDb3JwdXM6IFBhcmxWb3RlCkVhY2ggY29ycHVzIGRhdGEgZnJhbWUgY29uc2lzdHMgb2Y6ICAKLSBJRDogb3JpZ2luYWwgZG9jdW1lbnQgaWQgIAotIFRleHQ6IHdpbGwgYmUgdGhlIGlucHV0IHRvIHRoZSBzZW50aW1lbnQgYW5hbHlzaXMgIAotIFJhdGluZzogd2lsbCBiZSB0aGUgZ29sZCBzdGFuZGFyZCB0byBldmFsdWF0ZSBsZXhpY29uIHBlcmZvcm1hbmNlICAgIApgYGB7cn0KcGFybHZvdGUKYGBgCgojIyMgMi4gU3RlcDogU2VudGltZW50IEFuYWx5c2lzCiMjIyMgMi4xIE5vcm1hbGl6ZSBTY29yZXMKV2UgdXNlIGEgY3VzdG9taXplZCBtaW4vbWF4IG5vcm1hbGl6YXRpb24gbWV0aG9kIGhlcmUsIHRvIG1ha2Ugc3VyZSBhbGwgdmFsdWVzIG9mIHRoZSBBZmlubiBsZXhpY29uIGFyZSBzY2FsZWQgd2l0aCByZXNwZWN0IHRvIEFmaW5uJ3MgbWluaW11bSBsYWJlbCAoLTUpIGFuZCBtYXhpbXVtIGxhYmVsICgrNSkuIFRoZSBjb21wdXRlZCBzZW50aW1lbnQgc2NvcmVzIHdpbGwgYmUgbm9ybWFsaXplZCBiZXR3ZWVuIC0xIGFuZCArMS4gCmBgYHtyfQojID09PT09IERBVEEgTk9STUFMSVpBVElPTiA9PT09PQoKIyBOb3JtYWxpemUgZGF0YSB2aWEgbWluaW11bi9tYXhpbXVtIG5vcm1hbGl6YXRpb24sIGVpdGhlciBieSBzY2FsaW5nIHZhbHVlcyBmcm9tIDAgdG8gMSBvciAtMSB0byAxCiMgCiMgQXJnOgojICAgeDogaW5wdXQgdmFsdWVzIChlLmcuIGNvbHVtbiBvZiBkYXRhIGZyYW1lKQojICAgCiMgUmV0dXJuczogCiMgICBub3JtYWxpemVkIGRhdGEKCiMgbWluL21heCBub3JtYWxpemF0aW9uIGZyb20gLTEgdG8gMSwgcmVsYXRpdmUgdG8gZGF0YSBmcmFtZSByZXN1bHRzCm5vcm1hbGl6ZSA8LSBmdW5jdGlvbih4LCBuYS5ybSA9IFRSVUUpewogIHJldHVybigyKiAoKHggLSBtaW4oeCkpIC8gKG1heCh4KS1taW4oeCkpKS0xKX0KCiMgbWluL21heCBub3JtYWxpemF0aW9uIGZvciBhZmlubiBkYXRhLCB3cnQgdG8gYWZpbm4gc2NvcmluZyAoLTUsICs1KSAKbm9ybWFsaXplX2FmaW5uIDwtIGZ1bmN0aW9uKHgsIG5hLnJtID0gVFJVRSl7CiAgcmV0dXJuKDIqICgoeCAtICgtNSkpIC8gKDUtKC01KSkpLTEpfQpgYGAKCiMjIyMgMi4yIENvbXB1dGUgU2VudGltZW50IFNjb3JlcwpJbiB0aGlzIHN0ZXAgc2VudGltZW50IHNjb3JlcyBmb3IgdGhlIGRpZmZlcmVudCBsZXhpY29ucyAoQWZpbm4sIExTRCBhbmQgVmFkZXIpIGFyZSBjYWxjdWxhdGVkIGJ5IHVzaW5nIHRoZSBgdGV4c3RhdF92YWxlbmNlYCAoQWZpbm4pIGFuZCBgdGV4dHN0YXRfcG9sYXJpdHlgIChMU0QpIGZ1bmN0aW9ucyBvZiB0aGUgKnF1YW50ZWRhLnNlbnRpbWVudCogbGlicmFyeSBhbmQgdGhlIGB2YWRlcl9jb21wb3VuZGAgc2NvcmUgb2YgdGhlICp2YWRlciogbGlicmFyeS4gT3JpZ2luYWxseSB0d28gdmVyc2lvbnMgb2Ygbm9ybWFsaXphdGlvbiB3ZXJlIHRlc3RlZC4gRm9yIHRoZSBmaW5hbCBzZW50aW1lbnQgc2NvcmluZyB3ZSBkZWNpZGVkIG9uIHRoZSBgbm9ybWFsaXplX2FmaW5uYCBmdW5jdGlvbi4gCmBgYHtyfQojID09PT09IFNFTlRJTUVOVCBTQ09SRVMgPT09PT0KCiMgQ2FsY3VsYXRlIHNlbnRpbWVudCBzY29yZXMgZm9yIGRpZmZlcmVudCBsZXhpY29ucyBhbmQgaW5wdXQgZGF0YSBmcmFtZXMKIyAKIyBBcmc6CiMgIGRhdGE6IGlucHV0IGRhdGEgZnJhbWUKIyAgbGV4aWNvbnM6IG5hbWVzIG9mIGxleGljb25zIHRoYXQgc2hvdWxkIGJlIHVzZWQgZm9yIHNlbnRpbWVudCBzY29yaW5nCiMgIG5vcm1hbGl6ZTogInJlbGF0aXZlIiBpZiBub3JtYWxpemF0aW9uIGlzIGhhbmRsZWQgcmVsYXRpdmUgdG8gb3V0cHV0LCBpLmUuIG91dHB1dCBjb2x1bW4gb2YgYWZpbm4gaXMgYmVpbmcgc2NhbGVkIHZpYSBtaW4vbWF4IG5vcm1hbGl6YXRpb24KIyAgbm9ybWFsaXplOiAiYWZpbm4iIGlmIG5vcm1hbGl6YXRpb24gaXMgaGFuZGxlZCBieSB0YWtpbmcgLTUgYXMgbmV3IG1pbmltdW0gYW5kICs1IGFzIG5ldyBtYXhpbXVtIGFuZCBldmVyeXRoaW5nIGVsc2UgaXMgc2NhbGVkIGJldHdlZW4KIyAgZ2V0X3Rva2VuczogaWYgVFJVRSBmaW5hbCBkYXRhIGZyYW1lIGNvbnNpc3RzIG9mIHNpbmdsZSB0b2tlbnMgd2l0aCBhbiBhc3NvY2lhdGVkIHNlbnRpbWVudCBzY29yZQojICAgICAgICAgICAgICBlbHNlIGZpbmFsIGRhdGEgZnJhbWUgY29uc2lzdHMgb2YgYW4gYXNzb2NpYXRlZCBzZW50aW1lbnQgc2NvcmUgcGVyIGlucHV0IHRleHQgaW5zdGVhZCAob25seSB1c2VkIHRvIGNhbGN1bGF0ZSBUT1AtTiB3b3JkcykKIyAgIAojIFJldHVybnM6IAojICBkYXRhIGZyYW1lIHdpdGggKG5vcm1hbGl6ZWQpIHNlbnRpbWVudCBzb3JlcyBmb3IgY2hvc2VuIGxleGljb25zCgpnZXRfc2VudGltZW50IDwtIGZ1bmN0aW9uKGRmLCBsZXhpY29ucywgbm9ybWFsaXplLCBnZXRfdG9rZW5zKXsKICAgCiAgICMgaWYgd2Ugd2FudCBkYXRhIGZyYW1lIHdpdGggc2luZ2xlIHRva2VucyAKICAgaWYoZ2V0X3Rva2Vucz09VFJVRSl7CiAgICAgIGRmIDwtIGRmICU+JQogICAgICAgICAjIGdldCBsaXN0IG9mIHRva2VucyBhcyBuZXcgY29sIGluIGRhdGEgZnJhbWUKICAgICAgICAgdW5uZXN0X3Rva2Vucyh0b2tlbix0ZXh0KQogICAgICAKICAgICAgIyBhc3NpZ24gdGhlIG5ldyBjb2wgYXMgaW5wdXQgZm9yIHNlbnRpbWVudCBsZXhpY29ucwogICAgICB0b2sgPSBkZiR0b2tlbgogICAgIAogICAgICAjIGZvciBlYWNoIGxleGljb24sIGdldCBzZW50aW1lbnQgc2NvcmVzIGFuZCBzYXZlIHNjb3JlcyBpbiBuZXcgY29sdW1uIG9mIGRhdGEgZnJhbWUKICAgICAgZm9yKGxleCBpbiBsZXhpY29ucyl7CiAgICAgICAgIAogICAgICAgICBpZihsZXggPT0gImFmaW5uIil7CiAgICAgICAgICAgIGRmJGFmaW5uIDwtIHJvdW5kKHRleHRzdGF0X3ZhbGVuY2UodG9rLCBhZmlubiwgbm9ybWFsaXplPSJkaWN0aW9uYXJ5Iikkc2VudGltZW50LDMpfQogICAgICAgICBpZihsZXggPT0gImxzZCIpewogICAgICAgICAgICBkZiRsc2Q8LSByb3VuZCh0ZXh0c3RhdF9wb2xhcml0eSh0b2ssIGxzZCwgZnVuPXNlbnRfcmVscHJvcGRpZmYpJHNlbnRpbWVudCwzKX0KICAgICAKICAgICAgICAgaWYobGV4ID09ICJ2YWRlciIpewogICAgICAgICAgICBkZiR2YWRlciA8LSByb3VuZCh2YWRlcl9kZih0b2spJGNvbXBvdW5kLDMpfQogICB9CiAgICAKICAgIyBpZiB3ZSBkb24ndCB3YW50IHRvIGFuYWx5emUgc2luZ2xlIHRva2VucyBidXQgaW5wdXQgdGV4dCBhcyAid2hvbGUiCiAgIH1lbHNlewogICAgICAKICAgICAgdG9rID0gdG9rZW5zKGRmJHRleHQpCiAgICAgIAogICAgICBmb3IobGV4IGluIGxleGljb25zKXsKICAgICAgICAgaWYobGV4ID09ICJhZmlubiIpewogICAgICAgICAgICBkZiRhZmlubiA8LSByb3VuZCh0ZXh0c3RhdF92YWxlbmNlKHRvaywgYWZpbm4sIG5vcm1hbGl6ZT0iZGljdGlvbmFyeSIpJHNlbnRpbWVudCwzKX0KICAgICAgICAgCiAgICAgICAgIGlmKGxleCA9PSAibHNkIil7CiAgICAgICAgICAgIGRmJGxzZDwtIHJvdW5kKHRleHRzdGF0X3BvbGFyaXR5KHRvaywgbHNkLCBmdW49c2VudF9yZWxwcm9wZGlmZikkc2VudGltZW50LDMpfQogICAgICAgICAKICAgICAgICAgaWYobGV4ID09ICJ2YWRlciIpewogICAgICAgICAgICBkZiR2YWRlciA8LSByb3VuZCh2YWRlcl9kZihkZiR0ZXh0KSRjb21wb3VuZCwzKX0KICAgICB9CiAgIH0gIAogICAKICAgIyBub3JtYWxpemUgc2VudGltZW50IHNjb3JlcyBpZiBUUlVFLCBWQURFUiBhbmQgTFNEIHNjb3JlcyBhcmUgYWxyZWFkeSBub3JtYWxpemVkIHdpdGhpbiBmdW5jdGlvbnMgYWJvdmUKCiAgIGlmKG5vcm1hbGl6ZT09ImFmaW5uIil7CiAgICAgIGRmJGFmaW5uIDwtIHJvdW5kKG5vcm1hbGl6ZV9hZmlubihkZiRhZmlubiksIDMpCiAgIH1lbHNlewogICAgICBkZiRhZmlubiA8LSByb3VuZChub3JtYWxpemUoZGYkYWZpbm4pLCAzKX0KICAgCiAgICMgc2V0IHNlbnRpbWVudCBzY29yZXMgdG8gMCBpZiBOQQogICBkZltpcy5uYShkZildIDwtIDAKICAgCiAgIHJldHVybihkZikKfQoKIyBhcHBseSBnZXRfc2VudGltZW50IGZ1bmN0aW9uIHRvIGNvcnB1cyBkYXRhIAojIyMgQkFTRUxJTkUgIApyZXZpZXdzX3NlbnRpbWVudF9ub3JtMSA8LSBnZXRfc2VudGltZW50KHJldmlld3MsIGMoImFmaW5uIiwibHNkIiwgInZhZGVyIiksIG5vcm1hbGl6ZT0iYWZpbm4iLCBnZXRfdG9rZW5zPUZBTFNFKQp0d2l0dGVyX3NlbnRpbWVudF9ub3JtMSA8LSBnZXRfc2VudGltZW50KHR3aXR0ZXIsIGMoImFmaW5uIiwibHNkIiwgInZhZGVyIiksIG5vcm1hbGl6ZT0iYWZpbm4iLCBnZXRfdG9rZW5zPUZBTFNFKQpwYXJsdm90ZV9zZW50aW1lbnRfbm9ybTEgPC0gZ2V0X3NlbnRpbWVudChwYXJsdm90ZSwgYygiYWZpbm4iLCJsc2QiLCAidmFkZXIiKSwgbm9ybWFsaXplPSJhZmlubiIsIGdldF90b2tlbnM9RkFMU0UpCmFtYXpvbl9zZW50aW1lbnRfbm9ybTEgPC0gZ2V0X3NlbnRpbWVudChhbWF6b24sIGMoImFmaW5uIiwgImxzZCIsICJ2YWRlciIpLCBub3JtYWxpemU9ImFmaW5uIiwgZ2V0X3Rva2Vucz1GQUxTRSkKZmluYW5jZV9zZW50aW1lbnRfbm9ybTEgPC0gZ2V0X3NlbnRpbWVudChmaW5hbmNlLCBjKCJhZmlubiIsICJsc2QiLCAidmFkZXIiKSwgbm9ybWFsaXplPSJhZmlubiIsIGdldF90b2tlbnM9RkFMU0UpCnBhcmx2b3RlX2NvcnJfbm9ybTEgPC0gZ2V0X3NlbnRpbWVudChwYXJsdm90ZV9jb3JyLCBjKCJhZmlubiIsICJsc2QiLCAidmFkZXIiKSwgbm9ybWFsaXplPSJhZmlubiIsIGdldF90b2tlbnM9RkFMU0UpCgojIyMgQ09ORklHMgpyZXZpZXdzX2NvbmZpZzJfc2VudGltZW50X25vcm0xIDwtIGdldF9zZW50aW1lbnQocmV2aWV3c19jb25maWcyLCBjKCJhZmlubiIsImxzZCIsICJ2YWRlciIpLCBub3JtYWxpemU9ImFmaW5uIiwgZ2V0X3Rva2Vucz1GQUxTRSkKdHdpdHRlcl9jb25maWcyX3NlbnRpbWVudF9ub3JtMSA8LSBnZXRfc2VudGltZW50KHR3aXR0ZXJfY29uZmlnMiwgYygiYWZpbm4iLCJsc2QiLCAidmFkZXIiKSwgbm9ybWFsaXplPSJhZmlubiIsIGdldF90b2tlbnM9RkFMU0UpCnBhcmx2b3RlX2NvbmZpZzJfc2VudGltZW50X25vcm0xIDwtIGdldF9zZW50aW1lbnQocGFybHZvdGVfY29uZmlnMiwgYygiYWZpbm4iLCJsc2QiLCAidmFkZXIiKSwgbm9ybWFsaXplPSJhZmlubiIsIGdldF90b2tlbnM9RkFMU0UpCmFtYXpvbl9jb25maWcyX3NlbnRpbWVudF9ub3JtMSA8LSBnZXRfc2VudGltZW50KGFtYXpvbl9jb25maWcyLCBjKCJhZmlubiIsICJsc2QiLCAidmFkZXIiKSwgbm9ybWFsaXplPSJhZmlubiIsIGdldF90b2tlbnM9RkFMU0UpCmZpbmFuY2VfY29uZmlnMl9zZW50aW1lbnRfbm9ybTEgPC0gZ2V0X3NlbnRpbWVudChmaW5hbmNlX2NvbmZpZzIsIGMoImFmaW5uIiwgImxzZCIsICJ2YWRlciIpLCBub3JtYWxpemU9ImFmaW5uIiwgZ2V0X3Rva2Vucz1GQUxTRSkKYGBgCgojIyMgMy4gU3RlcCAoT3B0aW9uYWwpOiBDb252ZXJ0IERhdGEgaW50byBkaXNjcmV0ZSBmb3JtYXQKYGBge3J9CiMgQ29udmVydCBmaW5hbCB2YWx1ZXMgaW50byB0ZXJuYXJ5ICgxID0gcG9zaXRpdmUsIDAgPSBuZXV0cmFsLCAtMSA9IG5lZ2F0aXZlKSBmb3JtYXQgZm9yIGV2YWx1YXRpb24gYW5kIGNvbXBhcmlzb24KIyAKIyBBcmc6CiMgICBkZjogaW5wdXQgZGF0YSBmcmFtZSB0aGF0IGNvbnRhaW5zIHRoZSBjb2x1bW5zIHRvIGJlIGNvbnZlcnRlZCBpbnRvIHRlcm5hcnkgZm9ybWF0CiMgICB0b19jaGFuZ2U6IGNvbHVtbiBuYW1lcyB0aGF0IHNob3VsZCBiZSBjb252ZXJ0ZWQgaW50byB0ZXJuYXJ5IGZvcm1hdAojICAgCiMgUmV0dXJuczogCiMgICBkYXRhIGZyYW1lIHdpdGggY29udmVydGVkIHZhbHVlcwoKZ2V0X2Rpc2NyZXRlIDwtIGZ1bmN0aW9uKGRmLCB0b19jaGFuZ2UpewogIGRmICU+JSAKICAgIG11dGF0ZV9hdCh0b19jaGFuZ2UsIGZ1bmN0aW9uKHgpewogICAgICAjIG11dGF0ZSB2YWx1ZXMgZ3JlYXRlciB0aGFuIDAgdG8gMSAocG9zaXRpdmUpLCBlcXVhbCB0byAwIHRvIDAgKG5ldXRyYWwpIGFuZCBzbWFsbGVyIHRoYW4gMCB0byAtMSAobmVnYXRpdmUpCiAgICAgIGNhc2Vfd2hlbih4ID4gMCB+IDEsIHggPCAwIH4gLTEsIHggPT0gMCB+IDApfSkjICU+JSAKfQoKcmV2aWV3c19kaXNjcmV0ZSA8LSBnZXRfZGlzY3JldGUocmV2aWV3c19zZW50aW1lbnRfbm9ybTEsIGMoImFmaW5uIiwidmFkZXIiLCJsc2QiKSkKdHdpdHRlcl9kaXNjcmV0ZSA8LSBnZXRfZGlzY3JldGUodHdpdHRlcl9zZW50aW1lbnRfbm9ybTEsIGMoImFmaW5uIiwidmFkZXIiLCJsc2QiKSkKcGFybHZvdGVfZGlzY3JldGUgPC0gZ2V0X2Rpc2NyZXRlKHBhcmx2b3RlX3NlbnRpbWVudF9ub3JtMSwgYygiYWZpbm4iLCJ2YWRlciIsImxzZCIpKQphbWF6b25fZGlzY3JldGUgPC0gZ2V0X2Rpc2NyZXRlKGFtYXpvbl9zZW50aW1lbnRfbm9ybTEsIGMoImFmaW5uIiwidmFkZXIiLCJsc2QiKSkKZmluYW5jZV9kaXNjcmV0ZSA8LSBnZXRfZGlzY3JldGUoZmluYW5jZV9zZW50aW1lbnRfbm9ybTEsIGMoImFmaW5uIiwidmFkZXIiLCJsc2QiKSkKYGBgCgojIyMgNC4gU3RlcDogQ29tcHV0ZSBDb3ZlcmFnZQpUbyBjb21wdXRlIHRoZSBjb3ZlcmFnZSBvZiBlYWNoIHNlbnRpbWVudCBsZXhpY29uIGFuZCBmb3IgZWFjaCBjb3JwdXMsIHdlIGNvbnNpZGVyIGFsbCB0ZXh0cyBvciB0b2tlbnMgcmVjZWl2aW5nIGEgc2NvcmUgb2YgMCB0byBiZSBub3QtcmVjb2duaXplZCBieSB0aGUgYWNjb3JkaW5nIGxleGljb24sIGkuZS4gdGhlIHdvcmQgZG9lcyBub3QgZXhpc3QgaW4gdGhlIGxleGljb24gb3IgdGhlIHdob2xlIHRleHQgZGlkIG5vdCBjb250YWluIGEgd29yZCB3aGljaCBpcyBjb250YWluZWQgaW4gdGhlIGxleGljb24uIFdlIGNvbXB1dGUgdGhlIGNvdmVyYWdlIHBlciB0b2tlbiBhbmQgdGhlIGNvdmVyYWdlIHBlciB0ZXh0LiBJbiBjYXNlIG9mIHRoZSBjb3ZlcmFnZSBwZXIgdG9rZW4sIHdlIGNvbnNpZGVyIDIgY29uZmlndXJhdGlvbnM6IDEpIGEgYmFzZWxpbmUgY29uZmlndXJhdGlvbiB3aXRoIGEgc2ltcGxlIHRva2VuLWNvdW50IHRvIGdldCB0aGUgY292ZXJhZ2Ugc2NvcmUsIDIpIGEgY29uZmlndXJhdGlvbiBvZiB0aGUgdG9rZW5zIHdpdGggYSBwcmlvciBzdG9wd29yZCByZW1vdmFsLiAKYGBge3J9CiMgZ2V0IHNlbnRpbWVudCBmb3IgZWFjaCB0b2tlbiBpbiBjb3JwdXMKcmV2aWV3c190b2tfc2VudCA8LSBnZXRfc2VudGltZW50KHJldmlld3MsIGMoImFmaW5uIiwibHNkIiwgInZhZGVyIiksIG5vcm1hbGl6ZT0iYWZpbm4iLCBnZXRfdG9rZW5zPVRSVUUpCnR3aXR0ZXJfdG9rX3NlbnQgPC0gZ2V0X3NlbnRpbWVudCh0d2l0dGVyLCBjKCJhZmlubiIsImxzZCIsICJ2YWRlciIpLCBub3JtYWxpemU9ImFmaW5uIiwgZ2V0X3Rva2Vucz1UUlVFKQpwYXJsdm90ZV90b2tfc2VudCA8LSBnZXRfc2VudGltZW50KHBhcmx2b3RlLCBjKCJhZmlubiIsImxzZCIsICJ2YWRlciIpLCBub3JtYWxpemU9ImFmaW5uIiwgZ2V0X3Rva2Vucz1UUlVFKQphbWF6b25fdG9rX3NlbnQgPC0gZ2V0X3NlbnRpbWVudChhbWF6b24sIGMoImFmaW5uIiwibHNkIiwgInZhZGVyIiksIG5vcm1hbGl6ZT0iYWZpbm4iLCBnZXRfdG9rZW5zPVRSVUUpCmZpbmFuY2VfdG9rX3NlbnQgPC0gZ2V0X3NlbnRpbWVudChmaW5hbmNlLCBjKCJhZmlubiIsICJsc2QiLCAidmFkZXIiKSwgbm9ybWFsaXplPSJhZmlubiIsIGdldF90b2tlbnM9VFJVRSkKCgojIGNvbnZlcnQgdmFsdWVzIHRvIGRpc2NyZXRlIGZvcm1hdCAKcmV2aWV3c190b2tfZGlzY3JldGUgPC0gZ2V0X2Rpc2NyZXRlKHJldmlld3NfdG9rX3NlbnQsIGMoImFmaW5uIiwidmFkZXIiLCJsc2QiKSkKdHdpdHRlcl90b2tfZGlzY3JldGUgPC0gZ2V0X2Rpc2NyZXRlKHR3aXR0ZXJfdG9rX3NlbnQsIGMoImFmaW5uIiwidmFkZXIiLCJsc2QiKSkKcGFybHZvdGVfdG9rX2Rpc2NyZXRlIDwtIGdldF9kaXNjcmV0ZShwYXJsdm90ZV90b2tfc2VudCwgYygiYWZpbm4iLCJ2YWRlciIsImxzZCIpKQphbWF6b25fdG9rX2Rpc2NyZXRlIDwtIGdldF9kaXNjcmV0ZShhbWF6b25fdG9rX3NlbnQsIGMoImFmaW5uIiwidmFkZXIiLCJsc2QiKSkKZmluYW5jZV90b2tfZGlzY3JldGUgPC0gZ2V0X2Rpc2NyZXRlKGZpbmFuY2VfdG9rX3NlbnQsIGMoImFmaW5uIiwidmFkZXIiLCJsc2QiKSkKYGBgCgpgYGB7cn0KIyA9PT09PSBDT1ZFUkFHRSBQRVIgVE9LRU4gPT09PT0KCiMgQ292ZXJhZ2UgcGVyIHRva2VuOiBpZiBzZW50aW1lbnQgc2NvcmUgaXMgbm90IHplcm8sIGNvdW50IGFzICJjb3ZlcmVkIiwgZ2V0IHBlcmNlbnRhZ2Ugb2YgY292ZXJlZCB0b2tlbnMKcmV2aWV3c190b2tfY292ZXJhZ2UgPC0gYHJvd25hbWVzPC1gKGRhdGEuZnJhbWUodChyb3VuZChjb2xTdW1zKHJldmlld3NfdG9rX3NlbnRbYygiYWZpbm4iLCJsc2QiLCJ2YWRlciIpXSAhPSAwKS9jb2xTdW1zKHJldmlld3NfdG9rX3NlbnQgIT0gMClbWzFdXSoxMDAsMikpKSwgInJldmlld3MgdG9rZW5zIikKdHdpdHRlcl90b2tfY292ZXJhZ2UgPC0gYHJvd25hbWVzPC1gKGRhdGEuZnJhbWUodChyb3VuZChjb2xTdW1zKHR3aXR0ZXJfdG9rX3NlbnRbYygiYWZpbm4iLCJsc2QiLCJ2YWRlciIpXSAhPSAwKS9jb2xTdW1zKHR3aXR0ZXJfdG9rX3NlbnQgIT0gMClbWzFdXSoxMDAsMikpKSwgInR3aXR0ZXIgdG9rZW5zIikKcGFybHZvdGVfdG9rX2NvdmVyYWdlIDwtIGByb3duYW1lczwtYChkYXRhLmZyYW1lKHQocm91bmQoY29sU3VtcyhwYXJsdm90ZV90b2tfc2VudFtjKCJhZmlubiIsImxzZCIsInZhZGVyIildICE9IDApL2NvbFN1bXMocGFybHZvdGVfdG9rX3NlbnQgIT0gMClbWzFdXSoxMDAsMikpKSwgInBhcmx2b3RlIHRva2VucyIpCmFtYXpvbl90b2tfY292ZXJhZ2UgPC0gYHJvd25hbWVzPC1gKGRhdGEuZnJhbWUodChyb3VuZChjb2xTdW1zKGFtYXpvbl90b2tfc2VudFtjKCJhZmlubiIsImxzZCIsInZhZGVyIildICE9IDApL2NvbFN1bXMoYW1hem9uX3Rva19zZW50ICE9IDApW1sxXV0qMTAwLDIpKSksICJhbWF6b24gdG9rZW5zIikKZmluYW5jZV90b2tfY292ZXJhZ2UgPC0gYHJvd25hbWVzPC1gKGRhdGEuZnJhbWUodChyb3VuZChjb2xTdW1zKGZpbmFuY2VfdG9rX3NlbnRbYygiYWZpbm4iLCJsc2QiLCJ2YWRlciIpXSAhPSAwKS9jb2xTdW1zKGZpbmFuY2VfdG9rX3NlbnQgIT0gMClbWzFdXSoxMDAsMikpKSwgImZpbmFuY2UgdG9rZW5zIikKCiMgQ292ZXJhZ2UgcGVyIHRva2VuOiB3aXRoIHN0b3B3b3JkIHJlbW92YWwgCgojIHJlbW92ZSBzdG9wd29yZHMgZnJvbSBlYWNoIGRhdGEgZnJhbWUgCnJldmlld3NfdG9rLnN0b3B3b3JkcyA8LSByZXZpZXdzX3Rva19zZW50ICU+JQogICBhbnRpX2pvaW4oc3RvcF93b3JkcywgYnk9IGMoInRva2VuIiA9ICJ3b3JkIikgKSAlPiUKICAgdW5ncm91cCgpCgp0d2l0dGVyX3Rvay5zdG9wd29yZHMgPC0gdHdpdHRlcl90b2tfc2VudCAlPiUKICAgYW50aV9qb2luKHN0b3Bfd29yZHMsIGJ5PSBjKCJ0b2tlbiIgPSAid29yZCIpICkgJT4lCiAgIHVuZ3JvdXAoKQoKcGFybHZvdGVfdG9rLnN0b3B3b3JkcyA8LSBwYXJsdm90ZV90b2tfc2VudCAlPiUKICAgYW50aV9qb2luKHN0b3Bfd29yZHMsIGJ5PSBjKCJ0b2tlbiIgPSAid29yZCIpICkgJT4lCiAgIHVuZ3JvdXAoKQoKYW1hem9uX3Rvay5zdG9wd29yZHMgPC0gYW1hem9uX3Rva19zZW50ICU+JQogICBhbnRpX2pvaW4oc3RvcF93b3JkcywgYnk9IGMoInRva2VuIiA9ICJ3b3JkIikgKSAlPiUKICAgdW5ncm91cCgpCgpmaW5hbmNlX3Rvay5zdG9wd29yZHMgPC0gZmluYW5jZV90b2tfc2VudCAlPiUKICAgYW50aV9qb2luKHN0b3Bfd29yZHMsIGJ5PSBjKCJ0b2tlbiIgPSAid29yZCIpICkgJT4lCiAgIHVuZ3JvdXAoKQoKIyBzYW1lIGFzIGFib3ZlOiBpZiBzZW50aW1lbnQgc2NvcmUgaXMgbm90IHplcm8sIGNvdW50IGFzICJjb3ZlcmVkIiwgZ2V0IHBlcmNlbnRhZ2Ugb2YgY292ZXJlZCB0b2tlbnMKcmV2aWV3c190b2tfY292ZXJhZ2Uuc3RvcHdvcmRzIDwtIGByb3duYW1lczwtYChkYXRhLmZyYW1lKHQocm91bmQoY29sU3VtcyhyZXZpZXdzX3Rvay5zdG9wd29yZHNbYygiYWZpbm4iLCJsc2QiLCJ2YWRlciIpXSAhPSAwKS9jb2xTdW1zKHJldmlld3NfdG9rLnN0b3B3b3JkcyAhPSAwKVtbMV1dKjEwMCwyKSkpLCAicmV2aWV3cyB0b2tlbnMgLSBzdG9wd29yZHMiKQp0d2l0dGVyX3Rva19jb3ZlcmFnZS5zdG9wd29yZHMgPC0gYHJvd25hbWVzPC1gKGRhdGEuZnJhbWUodChyb3VuZChjb2xTdW1zKHR3aXR0ZXJfdG9rLnN0b3B3b3Jkc1tjKCJhZmlubiIsImxzZCIsInZhZGVyIildICE9IDApL2NvbFN1bXModHdpdHRlcl90b2suc3RvcHdvcmRzICE9IDApW1sxXV0qMTAwLDIpKSksICJ0d2l0dGVyIHRva2VucyAtIHN0b3B3b3JkcyIpCnBhcmx2b3RlX3Rva19jb3ZlcmFnZS5zdG9wd29yZHMgPC0gYHJvd25hbWVzPC1gKGRhdGEuZnJhbWUodChyb3VuZChjb2xTdW1zKHBhcmx2b3RlX3Rvay5zdG9wd29yZHNbYygiYWZpbm4iLCJsc2QiLCJ2YWRlciIpXSAhPSAwKS9jb2xTdW1zKHBhcmx2b3RlX3Rvay5zdG9wd29yZHMgIT0gMClbWzFdXSoxMDAsMikpKSwgInBhcmx2b3RlIHRva2VucyAtIHN0b3B3b3JkcyIpCmFtYXpvbl90b2tfY292ZXJhZ2Uuc3RvcHdvcmRzIDwtIGByb3duYW1lczwtYChkYXRhLmZyYW1lKHQocm91bmQoY29sU3VtcyhhbWF6b25fdG9rLnN0b3B3b3Jkc1tjKCJhZmlubiIsImxzZCIsInZhZGVyIildICE9IDApL2NvbFN1bXMoYW1hem9uX3Rvay5zdG9wd29yZHMgIT0gMClbWzFdXSoxMDAsMikpKSwgImFtYXpvbiB0b2tlbnMgLSBzdG9wd29yZHMiKQpmaW5hbmNlX3Rva19jb3ZlcmFnZS5zdG9wd29yZHMgPC0gYHJvd25hbWVzPC1gKGRhdGEuZnJhbWUodChyb3VuZChjb2xTdW1zKGZpbmFuY2VfdG9rLnN0b3B3b3Jkc1tjKCJhZmlubiIsImxzZCIsInZhZGVyIildICE9IDApL2NvbFN1bXMoZmluYW5jZV90b2suc3RvcHdvcmRzICE9IDApW1sxXV0qMTAwLDIpKSksICJmaW5hbmNlIHRva2VucyAtIHN0b3B3b3JkcyIpCgojID09PT09IENPVkVSQUdFIFBFUiBURVhUID09PT09CgojIENvdmVyYWdlIHBlciB0ZXh0OiBpZiBzZW50aW1lbnQgc2NvcmUgaXMgbm90IHplcm8sIGNvdW50IGFzICJjb3ZlcmVkIiwgZ2V0IHBlcmNlbnRhZ2Ugb2YgY292ZXJlZCB0ZXh0IGluc3RhbmNlcwpyZXZpZXdzX2NvdmVyYWdlIDwtIGByb3duYW1lczwtYChkYXRhLmZyYW1lKHQoY29sU3VtcyhyZXZpZXdzX3NlbnRpbWVudF9ub3JtMVtjKCJhZmlubiIsImxzZCIsInZhZGVyIildICE9IDApLzEwKSksICJyZXZpZXdzIHRleHQiKQp0d2l0dGVyX2NvdmVyYWdlIDwtIGByb3duYW1lczwtYChkYXRhLmZyYW1lKHQoY29sU3Vtcyh0d2l0dGVyX3NlbnRpbWVudF9ub3JtMVtjKCJhZmlubiIsImxzZCIsInZhZGVyIildICE9IDApLzEwKSksICJ0d2l0dGVyIHRleHQiKQpwYXJsdm90ZV9jb3ZlcmFnZSA8LSBgcm93bmFtZXM8LWAoZGF0YS5mcmFtZSh0KGNvbFN1bXMocGFybHZvdGVfc2VudGltZW50X25vcm0xW2MoImFmaW5uIiwibHNkIiwidmFkZXIiKV0gIT0gMCkvMTApKSwgInBhcmx2b3RlIHRleHQiKQphbWF6b25fY292ZXJhZ2UgPC0gYHJvd25hbWVzPC1gKGRhdGEuZnJhbWUodChjb2xTdW1zKGFtYXpvbl9zZW50aW1lbnRfbm9ybTFbYygiYWZpbm4iLCJsc2QiLCJ2YWRlciIpXSAhPSAwKS8xMCkpLCAiYW1hem9uIHRleHQiKQpmaW5hbmNlX2NvdmVyYWdlIDwtIGByb3duYW1lczwtYChkYXRhLmZyYW1lKHQoY29sU3VtcyhmaW5hbmNlX3NlbnRpbWVudF9ub3JtMVtjKCJhZmlubiIsImxzZCIsInZhZGVyIildICE9IDApLzEwKSksICJmaW5hbmNlIHRleHQiKQoKIyBzYXZlIGRhdGEgdG8gZGF0YSBmcmFtZSAKY292ZXJhZ2UgPC0gcmJpbmQocmV2aWV3c190b2tfY292ZXJhZ2UscmV2aWV3c190b2tfY292ZXJhZ2Uuc3RvcHdvcmRzLHJldmlld3NfY292ZXJhZ2UsIHR3aXR0ZXJfdG9rX2NvdmVyYWdlLHR3aXR0ZXJfdG9rX2NvdmVyYWdlLnN0b3B3b3JkcyxwYXJsdm90ZV90b2tfY292ZXJhZ2UsdHdpdHRlcl9jb3ZlcmFnZSxwYXJsdm90ZV90b2tfY292ZXJhZ2Uuc3RvcHdvcmRzLCBwYXJsdm90ZV9jb3ZlcmFnZSwgYW1hem9uX3Rva19jb3ZlcmFnZSxhbWF6b25fdG9rX2NvdmVyYWdlLnN0b3B3b3JkcywgYW1hem9uX2NvdmVyYWdlLCBmaW5hbmNlX3Rva19jb3ZlcmFnZSxmaW5hbmNlX3Rva19jb3ZlcmFnZS5zdG9wd29yZHMsIGZpbmFuY2VfY292ZXJhZ2UpCmBgYAoKRGlzcGxheSBDb3ZlcmFnZSBSZXN1bHRzCmBgYHtyfQpjb3ZlcmFnZQpgYGAKCiMjIyA1LiBTdGVwOiBQbG90IERhdGEKIyMjIyA1LjEgUGxvdCBTZW50aW1lbnQgU2NvcmVzCmBgYHtyfQojID09PT09IFBMT1QgU0VOVElNRU5UIFNDT1JFUyA9PT09PQoKIyBjcmVhdGUgZGF0YSBmcmFtZSB3aXRoIHNlbnRpbWVudCBzY29yZXMgYXMgdmFyaWFibGUgb2YgZmlyc3QgMTAwIGluc3RhbmNlcyBvZiBjb3JwdXMKcmV2aWV3c19kZiA8LSBtZWx0KGhlYWQocmV2aWV3c19zZW50aW1lbnRfbm9ybTEsMTAwKVssYygnaWQnLCdhZmlubicsJ2xzZCcsJ3ZhZGVyJyldLGlkLnZhcnMgPSAxKQp0d2l0dGVyX2RmIDwtIG1lbHQoaGVhZCh0d2l0dGVyX3NlbnRpbWVudF9ub3JtMSwxMDApWyxjKCdpZCcsJ2FmaW5uJywnbHNkJywndmFkZXInKV0saWQudmFycyA9IDEpCnBhcmx2b3RlX2RmIDwtIG1lbHQoaGVhZChwYXJsdm90ZV9zZW50aW1lbnRfbm9ybTEsMTAwKVssYygnaWQnLCdhZmlubicsJ2xzZCcsJ3ZhZGVyJyldLGlkLnZhcnMgPSAxKQphbWF6b25fZGYgPC0gbWVsdChoZWFkKGFtYXpvbl9zZW50aW1lbnRfbm9ybTEsMTAwKVssYygnaWQnLCdhZmlubicsJ2xzZCcsJ3ZhZGVyJyldLGlkLnZhcnMgPSAxKQpmaW5hbmNlX2RmIDwtIG1lbHQoaGVhZChmaW5hbmNlX3NlbnRpbWVudF9ub3JtMSwxMDApWyxjKCdpZCcsJ2FmaW5uJywnbHNkJywndmFkZXInKV0saWQudmFycyA9IDEpCgojIGNyZWF0ZSBwbG90cyBmb3IgZWFjaCBjb3JwdXMKcmV2aWV3c19wbG90IDwtIGdncGxvdChyZXZpZXdzX2RmLGFlcyh4ID0gaWQseSA9IHZhbHVlKSkgKyAKICAgICAgICAgICAgICAgIGdlb21fYmFyKGFlcyhmaWxsID0gdmFyaWFibGUpLHN0YXQgPSAiaWRlbnRpdHkiLHBvc2l0aW9uID0gImRvZGdlIikgKwogICAgICAgICAgICAgICAgZmFjZXRfd3JhcCh+IHZhcmlhYmxlLCBuY29sID0gMSwgc2NhbGVzPSJmcmVlX3kiKSsKICAgICAgICAgICAgICAgIHhsYWIoInRleHQgaWQiKSsgeWxhYigic2VudGltZW50IHNjb3JlIikrCiAgICAgICAgICAgICAgICBnZ3RpdGxlKCJSZXZpZXdzIFNlbnRpbWVudCIpCgp0d2l0dGVyX3Bsb3QgPC0gZ2dwbG90KHR3aXR0ZXJfZGYsYWVzKHggPSBpZCx5ID0gdmFsdWUpKSArIAogICAgICAgICAgICAgICAgZ2VvbV9iYXIoYWVzKGZpbGwgPSB2YXJpYWJsZSksc3RhdCA9ICJpZGVudGl0eSIscG9zaXRpb24gPSAiZG9kZ2UiKSArCiAgICAgICAgICAgICAgICB4bGFiKCJ0ZXh0IGlkIikrIHlsYWIoInNlbnRpbWVudCBzY29yZSIpKwogICAgICAgICAgICAgICAgZ2d0aXRsZSgiVHdpdHRlciBTZW50aW1lbnQiKQoKcGFybHZvdGVfcGxvdCA8LSBnZ3Bsb3QocGFybHZvdGVfZGYsYWVzKHggPSBpZCx5ID0gdmFsdWUpKSArIAogICAgICAgICAgICAgICAgIGdlb21fYmFyKGFlcyhmaWxsID0gdmFyaWFibGUpLHN0YXQgPSAiaWRlbnRpdHkiLHBvc2l0aW9uID0gImRvZGdlIikrCiAgICAgICAgICAgICAgICAgI2ZhY2V0X3dyYXAofiB2YXJpYWJsZSwgbmNvbCA9IDEsIHNjYWxlcz0iZnJlZV95IikrCiAgICAgICAgICAgICAgICAgeGxhYigidGV4dCBpZCIpKyB5bGFiKCJzZW50aW1lbnQgc2NvcmUiKSsKICAgICAgICAgICAgICAgICBnZ3RpdGxlKCJQYXJsVm90ZSBTZW50aW1lbnQiKQoKYW1hem9uX3Bsb3QgPC0gZ2dwbG90KGFtYXpvbl9kZixhZXMoeCA9IGlkLHkgPSB2YWx1ZSkpICsgCiAgICAgICAgICAgICAgIGdlb21fYmFyKGFlcyhmaWxsID0gdmFyaWFibGUpLHN0YXQgPSAiaWRlbnRpdHkiLHBvc2l0aW9uID0gImRvZGdlIikrCiAgICAgICAgICAgICAgICNmYWNldF93cmFwKH4gdmFyaWFibGUsIG5jb2wgPSAxLCBzY2FsZXM9ImZyZWVfeSIpKwogICAgICAgICAgICAgICB4bGFiKCJ0ZXh0IGlkIikrIHlsYWIoInNlbnRpbWVudCBzY29yZSIpKwogICAgICAgICAgICAgICBnZ3RpdGxlKCJBbWF6b24gU2VudGltZW50IikKCmZpbmFuY2VfcGxvdCA8LSBnZ3Bsb3QoZmluYW5jZV9kZixhZXMoeCA9IGlkLHkgPSB2YWx1ZSkpICsgCiAgICAgICAgICAgICAgICBnZW9tX2JhcihhZXMoZmlsbCA9IHZhcmlhYmxlKSxzdGF0ID0gImlkZW50aXR5Iixwb3NpdGlvbiA9ICJkb2RnZSIpKwogICAgICAgICAgICAgICAgI2ZhY2V0X3dyYXAofiB2YXJpYWJsZSwgbmNvbCA9IDEsIHNjYWxlcz0iZnJlZV95IikrCiAgICAgICAgICAgICAgICB4bGFiKCJ0ZXh0IGlkIikrIHlsYWIoInNlbnRpbWVudCBzY29yZSIpKwogICAgICAgICAgICAgICAgZ2d0aXRsZSgiRmluYW5jZSBTZW50aW1lbnQiKQoKcmV2aWV3c19wbG90LmxpbmUgPC0gZ2dwbG90KHJldmlld3NfZGYsYWVzKHggPSBpZCwgeSA9IHZhbHVlLCBncm91cD12YXJpYWJsZSkpICsKICAgICAgICAgICAgICAgICAgICAgZ2VvbV9saW5lKGFlcyhjb2xvdXI9dmFyaWFibGUpLCBzaXplPTAuNCkrIAogICAgICAgICAgICAgICAgICAgICB5bGltKC0xLDEpICsKICAgICAgICAgICAgICAgICAgICAgZ2d0aXRsZSgiUmV2aWV3cyBTZW50aW1lbnQgU2NvcmVzIikKCiMgc2hvdyBwbG90cwpyZXZpZXdzX3Bsb3QKcmV2aWV3c19wbG90LmxpbmUKdHdpdHRlcl9wbG90CnBhcmx2b3RlX3Bsb3QKYW1hem9uX3Bsb3QKZmluYW5jZV9wbG90IApgYGAKCiMjIyMgNS4yIFBsb3QgSW1wb3J0YW50IFdvcmRzClRvIHBsb3QgaW1wb3J0YW50IHdvcmRzIHBlciBzZW50aW1lbnQgd2UgdXNlIHRoZSBzZW50aW1lbnQgc2NvcmVzIGZvciBlYWNoIHRva2VuICh0aGF0IHdlIGNhbGN1bGF0ZWQgZm9yIHRoZSBjb3ZlcmFnZSBwYXJ0KSBhbmQgY29udmVydCBjb250aW51b3VzIHNjb3JlcyB0byBhIGRpc2NyZXRlIGZvcm1hdC4KYGBge3J9CiMgbG9hZCBzdG9wd29yZHMgCmRhdGEoc3RvcF93b3JkcykKYGBgCgpJbiB0aGUgbmV4dCBzdGVwLCB3b3JkIGNvdW50cyBmb3IgZWFjaCBkaXNjcmV0ZSAoc2VudGltZW50KSB2YXJpYWJsZSAoInBvc2l0aXZlIiwgIm5lZ2F0aXZlIiwgIm5ldXRyYWwiKSBhcmUgcmV0cmlldmVkIGZvciBlYWNoIGxleGljb24uCmBgYHtyfQojIGdldCB3b3JkIGNvdW50cyBwZXIgZGlzY3JldGUgdmFyaWFibGUgKCJwb3NpdGl2ZSIsICJuZWdhdGl2ZSIsICJuZXV0cmFsIiwgaS5lLiAxLC0xLDApCnJldmlld3NfYWZpbm4ud29yZF9jb3VudHMgPC0gcmV2aWV3c190b2tfZGlzY3JldGUgJT4lCiAgIGNvdW50KHRva2VuLCBhZmlubiwgc29ydCA9IFRSVUUpICU+JQogICBhbnRpX2pvaW4oc3RvcF93b3JkcywgYnk9IGMoInRva2VuIiA9ICJ3b3JkIikgKSAlPiUKICAgdW5ncm91cCgpCgpyZXZpZXdzX2xzZC53b3JkX2NvdW50cyA8LSByZXZpZXdzX3Rva19kaXNjcmV0ZSAlPiUKICAgY291bnQodG9rZW4sIGxzZCwgc29ydCA9IFRSVUUpICU+JQogICBhbnRpX2pvaW4oc3RvcF93b3JkcywgYnk9IGMoInRva2VuIiA9ICJ3b3JkIikgKSAlPiUKICAgdW5ncm91cCgpCgpyZXZpZXdzX3ZhZGVyLndvcmRfY291bnRzIDwtIHJldmlld3NfdG9rX2Rpc2NyZXRlICU+JQogICBjb3VudCh0b2tlbiwgdmFkZXIsIHNvcnQgPSBUUlVFKSAlPiUKICAgYW50aV9qb2luKHN0b3Bfd29yZHMsIGJ5PSBjKCJ0b2tlbiIgPSAid29yZCIpICkgJT4lCiAgIHVuZ3JvdXAoKQpgYGAKCk5vdyB3ZSBjYW4gcGxvdCB0aGUgdG9wIDIwIHdvcmRzIGZvciBlYWNoIHNlbnRpbWVudC4KYGBge3J9CiMgcGxvdCB0b3AgbiB3b3JkcyBmb3IgZWFjaCBzZW50aW1lbnQgYW5kIGZvciBlYWNoIGxleGljb24sIG4gPSAyMAp0b3BuX3Jldmlld3NfYWZpbm4ucGxvdCA8LSByZXZpZXdzX2FmaW5uLndvcmRfY291bnRzICU+JQogICAgICAgICBncm91cF9ieShhZmlubikgJT4lCiAgICAgICAgIHNsaWNlKDE6MjApICU+JQogICAgICAgICBnZ3Bsb3QoYWVzKHJlb3JkZXIodG9rZW4sIG4pLCBuLCBmaWxsID0gYWZpbm4pKSArCiAgICAgICAgICAgZ2VvbV9iYXIoYWxwaGEgPSAwLjgsIHN0YXQgPSAiaWRlbnRpdHkiLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgICAgICAgICAgZmFjZXRfd3JhcCh+YWZpbm4sIHNjYWxlcyA9ICJmcmVlX3kiKSArCiAgICAgICAgICAgbGFicyh5ID0gImNvbnRyaWJ1dGlvbiB0byBzZW50aW1lbnQiLCB4ID0gTlVMTCkgKwogICAgICAgICAgIGNvb3JkX2ZsaXAoKSsKICAgICAgICAgICBnZ3RpdGxlKCJSZXZpZXdzOiBUb3AgMjAgV29yZHMgcGVyIFNlbnRpbWVudCAoQWZpbm4pIikKIAp0b3BuX3Jldmlld3NfbHNkLnBsb3QgPC0gcmV2aWV3c19sc2Qud29yZF9jb3VudHMgJT4lCiAgICAgICAgIGdyb3VwX2J5KGxzZCkgJT4lCiAgICAgICAgIHNsaWNlKDE6MjApICU+JQogICAgICAgICBnZ3Bsb3QoYWVzKHJlb3JkZXIodG9rZW4sIG4pLCBuLCBmaWxsID0gbHNkKSkgKwogICAgICAgICAgIGdlb21fYmFyKGFscGhhID0gMC44LCBzdGF0ID0gImlkZW50aXR5Iiwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogICAgICAgICAgIGZhY2V0X3dyYXAofmxzZCwgc2NhbGVzID0gImZyZWVfeSIpICsKICAgICAgICAgICBsYWJzKHkgPSAiY29udHJpYnV0aW9uIHRvIHNlbnRpbWVudCIsIHggPSBOVUxMKSArCiAgICAgICAgICAgY29vcmRfZmxpcCgpKwogICAgICAgICAgIGdndGl0bGUoIlJldmlld3M6IFRvcCAyMCBXb3JkcyBwZXIgU2VudGltZW50IChMU0QpIikKIAp0b3BuX3Jldmlld3NfdmFkZXIucGxvdCA8LSByZXZpZXdzX3ZhZGVyLndvcmRfY291bnRzICU+JQogICAgICAgICBncm91cF9ieSh2YWRlcikgJT4lCiAgICAgICAgIHNsaWNlKDE6MjApICU+JQogICAgICAgICBnZ3Bsb3QoYWVzKHJlb3JkZXIodG9rZW4sIG4pLCBuLCBmaWxsID0gdmFkZXIpKSArCiAgICAgICAgICAgZ2VvbV9iYXIoYWxwaGEgPSAwLjgsIHN0YXQgPSAiaWRlbnRpdHkiLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgICAgICAgICAgZmFjZXRfd3JhcCh+dmFkZXIsIHNjYWxlcyA9ICJmcmVlX3kiKSArCiAgICAgICAgICAgbGFicyh5ID0gImNvbnRyaWJ1dGlvbiB0byBzZW50aW1lbnQiLCB4ID0gTlVMTCkgKwogICAgICAgICAgIGNvb3JkX2ZsaXAoKSsKICAgICAgICAgICBnZ3RpdGxlKCJSZXZpZXdzOiBUb3AgMjAgV29yZHMgcGVyIFNlbnRpbWVudCAoVkFERVIpIikKCnRvcG5fcmV2aWV3c19hZmlubi5wbG90CnRvcG5fcmV2aWV3c19sc2QucGxvdAp0b3BuX3Jldmlld3NfdmFkZXIucGxvdApgYGAKCgo=